iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
Software Development

用 NestJS 闖蕩微服務!系列 第 14

[用NestJS闖蕩微服務!] DAY14 - Prometheus (上)

  • 分享至 

  • xImage
  •  

微服務的監控

在微服務架構中,會同時存在多個服務,這些服務各自使用不同的技術、存在於不同的主機、有各自的生命週期,這使系統的 複雜度大幅提升,同時也讓 維運成本大幅提升,也因此建立起良好的 監控機制 是十分重要的,可以透過監控來定位問題、觀測潛在問題等,確保整個系統運作穩定。

雖然說監控很重要,但它要面對的挑戰也不少,像是:

  • 管理監控資料:每個服務每天都會產生大量的監控資料,這些資料會分散在多個服務甚至是不同節點上,要如何有效地管理這些資料是一大挑戰。
  • 資料格式:每個服務使用的技術、協議不同,產生出來的資料格式、類型也有可能因此不同。
  • 即時性:監控本身的時效性很高,尤其在微服務的架構下,很可能因為單一服務發生問題讓問題迅速擴散,所以即時的資料與分析結果十分重要。

Prometheus

Prometheus 是一套開源的監控、警示系統,用來收集和儲存 時間序列資料(Time Series Data),對於需要觀測一段時間或是某個時間點的變化變得容易很多,是一套非常受歡迎、廣泛應用在微服務架構中的監控系統。

補充:Prometheus 最早是由 SoundCloud 所創造,現已納入 CNCF 的項目。

Prometheus Logo

圖片來源

Prometheus 整體架構如下圖:

Prometheus Architecture

圖片來源

從上圖可以看到 Prometheus 是由多個元件所組成,包含:Prometheus Server、Pushgateway、Exporter、Alertmanager 等,後面將會針對這些元件做更詳細的說明。

資料模型

在 Prometheus 中,每一筆資料都是採用 時間序列(Time Series) 來儲存的,每個 Time Series 是由一個 指標(Metric) 名稱與一組 標籤(Label) 來標識,並由多個 樣本(Sample) 組成,這些 Sample 會包含 時間戳(Timestamp)值(Value)

用實際的例子來說明,我們定義了一個 Time Series 為「HTTP Code 為 200 的請求數量」,假如這個 Time Series 是由 3 個 Sample 構成且它們代表的意義如下:

  • sample1:在時間點 t1,累積 HTTP Code 200 的回應累積數量為 10
  • sample2:在時間點 t2,累積 HTTP Code 200 的回應累積數量為 30
  • sample3:在時間點 t3,累積 HTTP Code 200 的回應累積數量為 50

可以得知 sample1sample3 HTTP Code 200 的請求數量從 10 增加到 50,說明了這段時間內請求數量增加的趨勢。

Metrics

Metrics 是一串符合正則表達式 [a-zA-Z_:][a-zA-Z0-9_:]* 的字串,用來表示某個特定的測量,可以視為 Time Series 的名稱,如:prometheus_http_request_totalcpu_usage 等。

而 Metrics 又可以分成下列四種類型:

  1. Counter:這是一種累積指標,它只能夠持續增加或重置為 0,如:prometheus_http_requests_total 是用來計算 HTTP 請求的總數。
  2. Gauge:這是一種數值可以增加或減少的指標,如:cpu_temperature 是用來記錄 CPU 溫度的變化。
  3. Histogram:用來觀察分佈的指標,會將 Sample 分佈在一系列的 桶(Bucket) 裡,每一個 Bucket 即一個範圍,可以觀察符合某個 Bucket 的 Sample 數量,也可以觀察 Sample 總數與總和。
  4. Summary:用來觀察分佈的指標,不過與 Histogram 的差異在於 Summary 是採用 分位數(Quantiles) 來計算,如:第 50 個百分位數是多少。

Labels

Labels 是由 標籤名稱(Label Name)標籤值(Label Value) 組成的 key value 對,在設計上有以下限制:

  • Label Name 必須符合正則表達式 [a-zA-Z_][a-zA-Z0-9_]*
  • Label Name 在同一個 Time Series 中必須是唯一的。
  • Label Value 可以包含任何 Unicode 字元,但通常會使用 ASCII 字元。
  • 雙底線(__) 開頭的 Label Name 保留供內部使用。

下方是符合格式的 Label 範例:

{status="200", method="GET", path="/users"}

假如有一個 Metric 為 prometheus_http_requests_total,可以搭配 Label 來區分不同 HTTP Code、HTTP Method 與 Path,形成一個 Time Series。下方是範例格式,取得 HTTP Code 為 200、HTTP Method 為 GET 及 Path 為 /user 的累積請求數量:

prometheus_http_requests_total{status="200", method="GET", path="/user"}

Prometheus Server

Prometheus Server 是 Prometheus 生態中最核心的角色,負責收集、儲存及查詢 Time Series Data,它由三個元件所組成,分別是:收集引擎(Retrieval)時間序列資料庫(Time Series Database, TSDB)HTTP 伺服器(HTTP Server)

Retrieval

通常要監控的服務會提供路徑為 /metrics 的 Endpoint,讓 Retrieval 定期從該 Endpoint 拉取(Pull) 資料,並根據 Metric 的類型將資料轉換成內部的 Time Series 表示格式。

這些提供 /metrics Endpoint 的服務通常會使用 函式庫(Client Library) 來建立與產生相關資料,如果要監控的不是一個服務而是一個資料庫、伺服器等,像是:MySQL、Linux Server,可以透過 Exporter 來幫忙整理資料,並提供 /metrics Endpoint 讓 Retrieval Pull 資料。

補充:透過 Pull 的方式來取得資料可以避免大量服務主動 推送(Push) 監控資料造成網路阻塞。

補充:對 Client LibraryExporter 感興趣的朋友,可以參考官方文件的說明。

TSDB

TSDB 是 Prometheus 的儲存引擎,Retrieval Pull 下來的資料經轉換後就會由 TSDB 進行儲存。

HTTP Server

HTTP Server 提供了多個 API 讓使用者可以存取以獲取相關資料,甚至提供了 GUI 介面讓使用者可以透過 Prometheus 定義的查詢語言 - PromQL 來查詢、聚合 Time Series Data。

下方是 HTTP Server 提供的幾個 API:

  • /metrics:Prometheus 自身的監控 Endpoint,讓使用者可以監控 Prometheus Server 的狀態。
  • /api/v1/query:透過 PromQL 來查詢資料。
  • /api/v1/query_range:透過 PromQL 來查詢並獲取指定時間範圍內的資料。

補充:想深入了解有哪些 HTTP API 的朋友,可以參考官方文件的說明。

Pushgateway

在微服務架構中,可能存在一些無法提供 /metrics Endpoint 的服務,像是:Cronjob 等,面對這種情況,Prometheus 提供了 Pushgateway 的元件,讓這些服務主動 推送(Push) 資料到 Pushgateway,Retrieval 會定期從 Pushgateway Pull 資料。

Pushgateway Concept

Alertmanager

Alertmanager 會負責處理 Prometheus 的警報發送,發送的媒介可以是電子郵件、Slack 等,透過警報發送來主動告知維運人員系統出現異常,甚至是潛在風險,可以有效降低風險,建立起更加完備的監控機制。

Alertmanager Concept

PromQL

前面有提到 PromQL 是 Prometheus 定義的查詢語言,可以用來查詢、聚合 Time Series Data,透過 Prometheus GUI 可以使用 PromQL 將這些資料以圖表的方式呈現,或是讓外部服務經由 HTTP Server 提供的 API 來獲取資料。

資料型別

PromQL 共有四種資料型別,分別是:瞬時向量(Instant Vector)區間向量(Range Vector)純量(Scalar)字串(String),這些資料型別分別用於不同情況的查詢與分析需求。

Instant Vector

當我們直接透過一個 Metric 名稱、Label 進行查詢時,PromQL 會回傳符合條件的所有 Time Series 最新的值,這些值即 Instant Vector。

假設有 3 個 Sample:

  • 時間點 t1,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="200"} 的值為 10
  • 時間點 t2,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="404"} 的值為 50
  • 時間點 t3,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="200"} 的值為 200

如果在 t3 發生後立刻執行以下查詢:

prometheus_http_requests_total

查詢結果將會回傳 prometheus_http_requests_total{status="200"}t3 的值以及 prometheus_http_requests_total{status="404"}t2 的值,如下表所示:

Time Series
prometheus_http_requests_total{status="200"} 200
prometheus_http_requests_total{status="404"} 50
Range Vector

如果想要查詢一段時間內的 Sample,就可以使用 Range Vector,透過在 [] 中指定時間即可取出該段時間內的 Sample 集合。下方是查詢過去 5 分鐘內 Metric 名稱為 prometheus_http_requests_total 所有 Sample 的 PromQL 查詢條件:

prometheus_http_requests_total[5m]

上方範例可以看到 [5m] 的查詢條件,5持續時間(Time Durations)m單位(Unit),下方是 PromQL 提供的 Unit:

  • ms:毫秒。
  • s:秒。
  • m:分鐘。
  • h:小時。
  • d:天,即 24 小時。
  • w:週,即 7 天。
  • y:年,即 365 天。
Scalar

Scalar 是浮點數值,通常用於計算或是作為數值的篩選條件。

假設有 3 個 Sample:

  • 時間點 t1,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="200"} 的值為 10
  • 時間點 t2,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="404"} 的值為 50
  • 時間點 t3,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="200"} 的值為 200

如果這時候使用下方查詢條件將會把每個值都除以 10

prometheus_http_requests_total / 10

補充:上方 PromQL 中使用的 10 即為 Scalar。

結果如下表所示:

Time Series
prometheus_http_requests_total{status="200"} 20
prometheus_http_requests_total{status="404"} 5

除了運算值以外,還可以透過比較運算來篩選出符合條件的 Sample,使用下方查詢條件將會過濾出值大於 100 的結果:

prometheus_http_requests_total > 100

結果如下表所示:

Time Series
prometheus_http_requests_total{status="200"} 200
String

字串在 PromQL 通常會與 函數(Function) 一起使用,比如:針對 Label 做一些特殊處理等。

label_replace 函數當作範例,透過它來擴展新的 Label,並以指定 Label 的值作為擴展之 Label 的值。下方查詢條件會將 Label status 的值擴充至 Label code

label_replace(prometheus_http_requests_total, "code", "$1", "status", "(.+)")

補充:上方 PromQL 中使用 雙引號(") 的部分就是 String。

假設有 3 個 Sample:

  • 時間點 t1,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="200"} 的值為 10
  • 時間點 t2,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="404"} 的值為 50
  • 時間點 t3,Metric 名稱為 prometheus_http_requests_total 且 Label 為 {status="200"} 的值為 200

那麼使用上方 PromQL 會得到下表結果:

Time Series
prometheus_http_requests_total{status="200", code="200"} 200
prometheus_http_requests_total{status="404", code="404"} 50

函數

PromQL 提供了非常多種函數讓使用者可以計算出一些資訊來進一步監控、分析服務。由於函數非常多,不太可能在這篇文章逐一說明,所以下面會介紹幾個實務上較常用的函數供大家參考。

rate

rate 函數是用來計算指定時間範圍內 Time Series Data 的 平均增長率。下方是範例 PromQL 語法,透過 rate 函數得出過去 5 分鐘內 prometheus_http_request_duration_seconds_count{path="/"} 的平均增長率,進而觀察這段時間內該 API 處理時間的變化:

rate(prometheus_http_request_duration_seconds_count{path="/"}[5m])

假如在這 5 分鐘產生了 5 個 Sample:

  • 時間點 t1,值為 100
  • 時間點 t2,值為 105
  • 時間點 t3,值為 110
  • 時間點 t4,值為 115
  • 時間點 t5,值為 120

由上方範例可以鎖定 時間窗格(Time Window)5 分鐘,在這 5 分鐘內產生了 5 個 Sample,其中,最新的 Sample 為 t5、最舊的 Sample 為 t1,只要將 t5 的值減去 t1 的值,就可以得出增加的數量:

120 - 100 = 20

有了增加的數量之後,就可以來計算平均增長率,這裡是以 為單位,所以我們將 5 分鐘轉換為 300 秒,接著,以 增加的數量/總秒數 的公式進行計算:

20 / 300 = 0.06667

最終得出的數值 0.06667prometheus_http_request_duration_seconds_count{path="/"}5 分鐘內的平均增長率。

irate

irate 函數是用來計算指定時間範圍內 Time Series Data 的 瞬間增長率。下方是範例 PromQL 語法,透過 irate 函數得出過去 5 分鐘內 prometheus_http_request_duration_seconds_count{path="/"} 的瞬間增長率,進而觀察這段時間內該 API 處理時間的 突發變化

irate(prometheus_http_request_duration_seconds_count{path="/"}[5m])

假如在這 5 分鐘產生了 5 個 Sample:

  • 時間點 t1,發生於第 60 秒,值為 100
  • 時間點 t2,發生於第 120 秒,值為 105
  • 時間點 t3,發生於第 180 秒,值為 110
  • 時間點 t4,發生於第 240 秒,值為 115
  • 時間點 t5,發生於第 300 秒,值為 120

由上方範例可以鎖定 Time Window 為 5 分鐘,在這 5 分鐘內產生了 5 個 Sample,我們要從這 5 個 Sample 中取樣,瞬間增長率取的是 最新的兩個 Sample 來計算增加的數量,也就是拿 t5 的值與 t4 的值相減:

120 - 115 = 5

得出增加的數量之後,就要來取 這兩個 Sample 的時間差,也就是拿 t5 的時間與 t4 的時間相減:

300 - 240 = 60

最後,就可以拿增加的數量與時間差相除來得出瞬間增長率:

5 / 60 = 0.08334

最終得出的數值 0.08334prometheus_http_request_duration_seconds_count{path="/"}5 分鐘內的瞬增長率。

安裝 Prometheus

在終端機輸入下方指令以便從 Docker Hub 下載 Prometheus 的 Docker Image:

$ docker pull prom/prometheus

Prometheus 可以透過撰寫 YAML 檔來進行設定,像是:抓取資料的頻率、抓取資料的目標等。透下方指令產生 prometheus.yml

$ mkdir <FOLDER>
$ touch <FOLDER>/prometheus.yml

接著,調整 prometheus.yml 的內容,透過 global 來定義全域設定,以下方範例來說,設定 scrape_interval5s 讓 Prometheus 以每 5 秒的頻率抓取資料。透過設置 scrape_configs 來設定抓取資料的相關設定,首先,需要指定 job_name 來替該 Job 命名,然後可以透過 static_configs 來設定靜態目標,以下方範例來說,我們指定目標為 Prometheus 本身:

global:
  scrape_interval: 5s
scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

補充:在複雜的微服務架構下,會更傾向用 服務發現機制(Service Discovery) 的方式來找到目標並抓取資料,不過為了讓內容保持簡單易懂,這裡使用靜態的方式來說明。

設定完了以後,就可以透過下方指令將 Prometheus 架設起來,運用 Docker 相關指令來將剛剛建立的資料夾與 prometheus.yml 綁定至 Container 中,並將 port 指定為 9090

$ docker run --name <NAME> -p 9090:9090 -v /<FOLDER>:/etc/prometheus prom/prometheus

打開瀏覽器並存取 http://localhost:9090,會看到 Prometheus 提供的 GUI:

Prometheus GUI

操作 Prometheus GUI

透過 Prometheus GUI 我們可以在「Graph」頁面使用 PromQL 來查詢 Time Series Data,也可以運用函數來繪製出有意義的圖表。在頁面上方的輸入框直接輸入 prometheus_http_requests_total 就可以找出所有 Metric 名稱為 prometheus_http_requests_total 的 Time Series,這些 Time Series 的值會取最新 Sample 的值,也就是 Instant Vector:

Prometheus GUI Result1

當然也可以透過 Label 來過濾資料,以下方範例來說,在輸入框輸入 prometheus_http_requests_total{code="200", handler="/metrics", instance="localhost:9090", job="prometheus"} 即可過濾出該 Time Series:

Prometheus GUI Result2

最後,來試試看繪製圖表,將頁面下方的頁籤切換至「Graph」並在上方輸入框輸入 irate(process_cpu_seconds_total[1h]),以觀察 Time Window 為 1 小時的狀況下, CPU 使用時間的瞬間增長率:

Prometheus GUI Result3

注意:圖表的上方有一個時間選擇器,該時間選擇器是用來決定圖表的起始時間與結束時間,以上方範例來說,Prometheus GUI 會以 1 小時的 Time Window 去繪製過去 2 小時 CPU 使用時間的瞬間增長率。

小結

Prometheus 是一套強大的監控系統,適合用於微服務架構,透過這篇文章,我們可以對 Prometheus 有更進一步的認識,包含其基本概念、組成的元件及用於查詢的 PromQL。

有了 Prometheus 的相關知識以後,下一篇將會結合 NestJS 來實現對 NestJS 服務的監控,敬請期待!


上一篇
[用NestJS闖蕩微服務!] DAY13 - Health Check
下一篇
[用NestJS闖蕩微服務!] DAY15 - Prometheus (下)
系列文
用 NestJS 闖蕩微服務!21
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言